package matrices;

public class SparseMatrix implements Matrix{
	public static IllegalArgumentException rowColException =
					new IllegalArgumentException("Row Number or Column Number illegal");
	final int rows, columns;
	SparseRow [] rowPointers;//rows of the matrix
	private int [] rowNumbers;
	MatrixElement defaultValue;
	
	/*******************************************************************
	 * CONSTRUCTORS
	 */
	
	public SparseMatrix(int rows, int columns , MatrixElement defaultValue){
		this.rows = rows;
		this.columns = columns;
		this.rowPointers = new SparseRow [0];
		this.rowNumbers = new int [0];
		this.defaultValue = defaultValue;
	}
	
	/**
	 * Copy constructor
	 * @param origional
	 */
	public SparseMatrix(SparseMatrix origional){
		this.rows = origional.rows;
		this.columns = origional.columns;
		this.defaultValue = origional.defaultValue.createNewInstance();
		this.rowNumbers = new int[origional.rowNumbers.length];
		this.rowPointers = new SparseRow[origional.rowPointers.length];
		for(int i = 0 ; i < origional.rowNumbers.length ; i++){
			this.rowNumbers[i] = origional.rowNumbers[i];
			this.rowPointers[i] = new SparseRow(origional.rowPointers[i]);
		}
	}
	
	public SparseMatrix(int columns, SparseRow [] rows) throws IllegalArgumentException {//for creating a matrix from a transpose	
		for(SparseRow row : rows ) if(row.getLargestColumnNumber() > columns)
			throw new IllegalArgumentException("Conflict between number of columns specified and columns in row");
		
		this.rowNumbers = new int[rows.length];
		for(int i = 0 ; i < rows.length ; i++) this.rowNumbers[i] = i;
		this.defaultValue = rows[0].getValueOfDefaultElement();
		this.rows = rows.length;
		this.columns = columns;
		this.rowPointers = rows;
	}
	
	public SparseMatrix(SparseRow [] rows){
		int maxCol = 0;
		for(SparseRow row : rows) if(maxCol < row.getLargestColumnNumber()) maxCol = row.getLargestColumnNumber();
		this.rowNumbers = new int[rows.length];
		for(int i = 0 ; i < rows.length ; i++) rowNumbers[i] = i;
		this.rows = rows.length;
		this.columns = maxCol+1;
		this.rowPointers = rows;
		this.defaultValue = rows[0].getValueOfDefaultElement();
	}
	
	/*******************************************************************
	 * PUBLICLY AVAILABLE RETRIEVAL METHODS
	 */
	
	public int getRows() {
		return this.rows;
	}
	
	public int getColumns() {
		return this.columns;
	}
	
	public MatrixElement getDefaultElementValue(){
		return this.defaultValue.createNewInstance();
	}
	
	public MatrixElement getElementValueAt(int row, int column)
			throws IllegalArgumentException {
		this.checkLegalCoords(row, column);
		return this.getRowPointer(row).getElementValueByColumn(column).createNewInstance();
	}
	
	public MatrixElement getElementPointerAt(int row , int column){
		this.checkLegalCoords(row,column);
		return this.getRowPointer(row).getElementValueByColumn(column);
	}
	
	public MatrixElement getDefaultElementPointer() {
		return this.defaultValue;
	}
	
	public String toString(){
		String returnStr = "" + (this.rows) + " by " +
				(this.columns) + " " + this.getClass().getName() + " default value " + this.defaultValue;
		returnStr += getPrintOut();
		return returnStr;
	}
	public String getPrintOut(){
		String returnStr = "";
		for(int i = 0 ; i < this.rows ; i++) returnStr += "\n" + this.getRowPointer(i).printRow(this.columns-1);
		return returnStr;
	}
	
	public SparseRow getRowPointer(int row){
		checkLegalRow(row);
		int index = indexFromRowAddress(row);
		if(index != -1)return this.rowPointers[index];
		return new SparseRow(this.defaultValue);
	}
	
	//TODO lazy implementation, but i don't think its likely well need this method much anyway
	public Matrix getTranspose(){
		SparseMatrix returnMatrix = new SparseMatrix(this.columns,this.rows,this.defaultValue);
		for(int i = 0 ; i < this.rows ; i++){
			for(int j = 0 ; j < this.columns ; j++){
				returnMatrix.setElementValueAt(j, i, 
						this.getElementValueAt(i, j));
			}
		}
		return returnMatrix;
	}
	
	/*******************************************************************
	 * PUBLICLY AVAILABLE ASSIGNMENT METHODS
	 */
	
	public void setElementPointerAt(int row, int column,
			MatrixElement newElement) throws IllegalArgumentException {
		this.checkLegalCoords(row, column);
		int index = this.indexFromRowAddress(row);
		if(index != -1){
			this.rowPointers[index].setElementValue(column, newElement.createNewInstance());
		}else{
			SparseRow p = new SparseRow(this.defaultValue);
			p.setElementValue(column, newElement);
			this.addRow(row, p);

		}
	}
	
	public void setElementValueAt(int row, int column, MatrixElement newElement)
			throws IllegalArgumentException {
		this.checkLegalCoords(row, column);
		int index = this.indexFromRowAddress(row);
		if(index != -1){
			this.rowPointers[index].setElementValue(column, newElement.createNewInstance());
		}else{
			SparseRow p = new SparseRow(this.defaultValue);
			p.setElementValue(column, newElement.createNewInstance());
			this.addRow(row, p);
		}
	}
	
	public void setDefaultElementValue(MatrixElement value) {
		this.defaultValue = value.createNewInstance();
	}

	public void setDefaultElementPointer(MatrixElement pointer) {
		this.defaultValue = pointer;
	}
	
	
	public SparseMatrix addRow(int row, SparseRow rowToAdd){
		checkLegalCoords(row, rowToAdd.getLargestColumnNumber());
		int index = indexFromRowAddress(row);
		if (index == -1){
			addArrayOfRows(row, rowToAdd);
		}else{
			this.rowPointers[index] = this.rowPointers[index].combineWith(rowToAdd);
		}
		return this;
	}
	
	public SparseMatrix clean(){
		//removes empty rows
		int [] newRowNumbers = new int[this.rowNumbers.length];
		SparseRow [] newRowPointers = new SparseRow[this.rowPointers.length];
		int nonEmptyRows = 0;
		for(int i = 0 ; i < this.rowNumbers.length ; i++){
			if(this.rowPointers[i].isEmpty()){
				delete(i);
			}else{
				newRowNumbers[nonEmptyRows] = this.rowNumbers[i];
				newRowPointers[nonEmptyRows] = this.rowPointers[i];
				nonEmptyRows ++;
			}
		}
		
		//copy over the new arrays
		this.rowPointers = new SparseRow[nonEmptyRows];
		this.rowNumbers = new int[nonEmptyRows];
		for(int i = 0 ; i < nonEmptyRows ; i++){
			this.rowPointers[i] = newRowPointers[i];
			this.rowNumbers[i] = newRowNumbers[i];
		}
		return this;
	}
	
	public SparseMatrix deleteRow(int row){
		checkLegalRow(row);
		for(int i = 0 ; i < this.rowNumbers.length ; i++)
			if(this.rowNumbers[i] == row) this.rowPointers[i] = new SparseRow(defaultValue);
		return this;
	}
	
	/*******************************************************************
	 * PRIVATE METHODS
	 */
	
	private void checkLegalRow(int row){ if(row < 0 || row >= this.rows) throw rowColException; }
	
	private void checkLegalCol(int col){ if(col < 0 || col >= this.columns) throw rowColException; }
	
	private void checkLegalCoords(int row, int col){ checkLegalRow(row) ; checkLegalCol(col) ; }
	
	private void addArrayOfRows(int row , SparseRow ... pointers){
		//check that the resulting operation will not violate the matrix in any way
		checkLegalRow(row);
		checkLegalRow(row + pointers.length - 1);
		for(SparseRow pointer : pointers) checkLegalCol(pointer.getLargestColumnNumber());
		
		//create new arrays to hold the added values
		int [] newRowNumbers = new int[this.rowNumbers.length + pointers.length];
		SparseRow [] newRowPointers = new SparseRow[this.rowPointers.length + pointers.length];
		for(int i = 0 ; i < newRowPointers.length ; i++){
			if(i < this.rowNumbers.length){
				newRowNumbers[i] = this.rowNumbers[i];
				newRowPointers[i] = this.rowPointers[i];//copy over the origional array
			}else{
				newRowNumbers[i] = row + (i - this.rowNumbers.length);
				newRowPointers[i] = pointers[i - this.rowPointers.length];
			}
		}
		
		//reassign the values to the member arrays
		this.rowNumbers = newRowNumbers;
		this.rowPointers = newRowPointers;
	}
	
	private int indexFromRowAddress(int row){
		checkLegalRow(row);
		checkLegalRow(row);
		//find the index corresponding to that row
		int index = -1;
		for(int i = 0 ; i < this.rowNumbers.length ; i++)
			if(this.rowNumbers[i] == row) index = i;
		return index;
	}
	
	private void delete(int row){
		checkLegalRow(row);
		this.rowPointers[row] = new SparseRow(this.defaultValue);
	}
	
	
	/*******************************************************************
	 * MATRIX OPERATION METHODS
	 */
	
	public Matrix mMultiply(Matrix matrix) throws IllegalArgumentException {
		// TODO Auto-generated method stub
		return null;
	}
	
	//TODO lazy implementation
	public Matrix mAdd(Matrix... matrices) {
		for(Matrix matrix : matrices){
			if(matrix.getDefaultElementValue().getClass() != this.getDefaultElementValue().getClass())
				throw new IllegalArgumentException("Matrix types are incompatible");
			checkLegalCoords(matrix.getRows()-1, matrix.getColumns()-1);
			
			for(int i = 0 ; i < matrix.getRows(); i++){
				for(int j = 0 ; j < matrix.getColumns() ; j++){
					this.setElementValueAt(i, j, 
							this.getElementValueAt(i, j).add( matrix.getElementValueAt(i, j) )
							);
				}
			}
		}
		return this;
	}
	
	public Matrix mSubtract(Matrix matrix) {
			if(matrix.getDefaultElementValue().getClass() != this.getDefaultElementValue().getClass())
				throw new IllegalArgumentException("Matrix types are incompatible");
			checkLegalCoords(matrix.getRows()-1, matrix.getColumns()-1);
			
			MatrixElement temp;
			
			for(int i = 0 ; i < matrix.getRows(); i++){
				//System.out.println("Row " + i);
				for(int j = 0 ; j < matrix.getColumns() ; j++){
					//System.out.println("Subtracting " + matrix.getElement(i, j) + " from " + this.getElement(i, j));
					this.setElementValueAt(i, j, 
							this.getElementValueAt(i, j).subtract( matrix.getElementValueAt(i, j) )
							);
					//System.out.println("\tresult " + this.getElement(i, j));
				}
			}
		return this;
	}
	
	public Matrix scalarMultiply(MatrixElement scale) {
		for(int i = 0 ; i < this.rowPointers.length ; i++)
			this.rowPointers[i] = this.rowPointers[i].multiplyElementsBy(scale);
		return this;
	}
	
	public Matrix scaleDefaultValue(MatrixElement scale){
		this.defaultValue = this.defaultValue.multiply(scale);
		return this;
	}
	

	
	/******************************************************************
	 * Methods that may not be necessary, I will implement them if there is sufficient time & willpower
	 */
	
	//will be implemented later if neccessary
	public MatrixElement getDeterminant() {
		// TODO Auto-generated method stub
		return null;
	}

	//will be implemented later if neccessary
	public Matrix getEigenvalues() {
		// TODO Auto-generated method stub
		return null;
	}

	//will be implemented later if neccessary
	public Matrix getInverse() {
		// TODO Auto-generated method stub
		return null;
	}

}
